home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Sample Code / SCSI Samples 1.0 / SCSI Async Sample 06⁄15 ƒ / Src / LogManager.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-06-16  |  33.5 KB  |  1,321 lines  |  [TEXT/KAHL]

  1. /*                                    LogManager.c                                */
  2. /*
  3.  * LogManager.c
  4.  * Copyright © 1993 Apple Computer Inc. All rights reserved.
  5.  *
  6.  * These functions manage a logging display for error messages and other text.
  7.  * The log is implemented as a ListManager list that can hold nLogItems. This
  8.  * module is intentionally more-or-less self-contained so it can easily be
  9.  * exported to other applications.
  10.  */
  11. #include "LogManager.h"
  12. #ifndef THINK_C                /* MPW includes            */
  13. #include <Errors.h>
  14. #include <Script.h>
  15. #include <Types.h>
  16. #include <Files.h>
  17. #include <Resources.h>
  18. #include <QuickDraw.h>
  19. #include <Fonts.h>
  20. #include <Events.h>
  21. #include <Windows.h>
  22. #include <ToolUtils.h>
  23. #include <Memory.h>
  24. #include <Menus.h>
  25. #include <Lists.h>
  26. #include <Printing.h>
  27. #include <Dialogs.h>
  28. #include <StandardFile.h>
  29. #endif
  30. #include <Serial.h>
  31. #include <Printing.h>
  32. #include <Packages.h>
  33. #pragma segment LogManager
  34.  
  35. /*
  36.  * There is a 32000 byte maximum for the list, so don't
  37.  * make nLogLines too big.
  38.  */
  39. #ifndef nDefaultLogLines
  40. #define nDefaultLogLines    128
  41. #endif
  42. #define width(r)                ((r).right - (r).left)
  43. #define height(r)                ((r).bottom - (r).top)
  44. #ifndef TRUE
  45. #define TRUE                    1
  46. #define FALSE                    0
  47. #endif
  48. enum {
  49.     kScrollBarWidth     = 16,
  50.     kScrollBarOffset    = kScrollBarWidth - 1,
  51.     kActiveControl        = 0,        /* Normal button hilite                */
  52.     kDisabledControl    = 255        /* Disabled button hilite            */
  53. };
  54. /*
  55.  * When the horizontal scrollbar is at zero, the list is indented +4 pixels.
  56.  * When the horizontal scrollbar increases, the list indent decreases. This
  57.  * value is "by inspection," but we could extract it from the list record
  58.  * when the list is initially created.
  59.  */
  60. enum {
  61.     kZeroIndent            = 4
  62. };
  63. /*
  64.  * This sets a nominal value for the maximum cell width. This might be
  65.  * better provided as a user-settable parameter.
  66.  */
  67. #define kMaxHorizontalScroll    (CharWidth('M') * 255)
  68. static const char        endOfLine[1] = { 0x0D };    /* <CR>            */
  69.  
  70. /*
  71.  * Cheap 'n dirty pascal string copy routine.
  72.  */
  73. #ifndef pstrcpy
  74. #define pstrcpy(dst, src) do {                            \
  75.         StringPtr    _src = (src);                        \
  76.         BlockMove(_src, dst, _src[0] + 1);                \
  77.     } while (0)
  78. #endif
  79. /*
  80.  * Cheap 'n dirty pascal string concat.
  81.  */
  82. #ifndef pstrcat
  83. #define pstrcat(dst, src) do {                            \
  84.         StringPtr        _dst = (dst);                    \
  85.         StringPtr        _src = (src);                    \
  86.         short            _len;                            \
  87.         _len = 255 - _dst[0];                            \
  88.         if (_len > _src[0]) _len = _src[0];                \
  89.         BlockMove(&_src[1], &_dst[1] + _dst[0], _len);    \
  90.         _dst[0] += _len;                                \
  91.     } while (0)
  92. #endif
  93.  
  94. static OSErr                        AddStringToList(
  95.         ListHandle                        logListHandle,
  96.         ConstStr255Param                theString
  97.     );
  98. static pascal void                    ScrollLogAction(
  99.         register ControlHandle            theControl,
  100.         short                            partcode
  101.     );
  102. static void                            ScrollLogList(
  103.         ControlHandle                    theControl
  104.     );
  105. static StringHandle                    GetLogStringHandle(
  106.         ListHandle                        logListHandle,
  107.         short                            theRow
  108.     );
  109.  
  110. #define LIST            (**logListHandle)
  111. #define LOGINFO            (**((LogInfoHdl) (LIST.userHandle)))
  112. #define HSCROLL            (LOGINFO.hScroll)
  113. #define IS_COLOR(port)    (((((CGrafPtr) (port))->portVersion) & 0xC000) != 0)
  114. #define COLOR_LIST        (IS_COLOR(LIST.port))
  115.  
  116. /*
  117.  * This code sequence is copied into the ListProc handle. It is designed so we
  118.  * don't have to flush the instruction and data caches. Note that this must
  119.  * be revised if you compile for a non-68000 environment.
  120.  */
  121. static const short gDummyLDEF[] = {
  122.         0x207A,                /*        movea.l    procPtr,a0            */
  123.         0x0004,                /*                <offset to procPtr>    */
  124.         0x4ED0,                /*        jmp        a0                     */
  125.         0x0000, 0x0000        /*        dc.l    <Drawing Proc here>    */
  126. };
  127.  
  128. static pascal void
  129. LogListDefProc(
  130.         short                    listMessage,
  131.         Boolean                    listSelect,
  132.         Rect                    *listRect,
  133.         Cell                    listCell,
  134.         short                    listDataOffset,
  135.         short                    listDataLen,
  136.         ListHandle                errorLogList
  137.     );
  138.  
  139. /*
  140.  * Create the data display list.
  141.  */
  142. ListHandle
  143. CreateLog(
  144.         const Rect                    *viewRect,
  145.         short                        listFontNumber,
  146.         short                        listFontSize,
  147.         short                        logLines
  148.     )
  149. {
  150.         OSErr                        status;
  151.         ListHandle                    logListHandle;
  152.         FontInfo                    info;
  153.         Point                        cellSize;
  154.         short                        listHeight;
  155.         Rect                        dataBounds;
  156.         Rect                        listRect;
  157.         short                        listFontHeight;
  158.         Handle                        drawProcHdl;
  159.         LogInfoRecord                logInfo;
  160.         Handle                        logInfoHdl;
  161.         ProcPtr                        listProcPtr;
  162.         short                        horizontalMax;
  163.         
  164.         logInfoHdl = NULL;
  165.         drawProcHdl = NULL;
  166.         if (logLines == 0)
  167.             logLines = nDefaultLogLines;
  168.         TextFont(listFontNumber);
  169.         TextSize(listFontSize);
  170.         GetFontInfo(&info);
  171.         listFontHeight = info.ascent + info.descent + info.leading;
  172.         /*
  173.          * Compute the list drawing area, adjusting the list
  174.          * area height so an integral number of lines will be drawn. As with
  175.          * the standard list manager, the scroll bars will be drawn outside
  176.          * of the list rectangle.
  177.          */
  178.         listRect = *viewRect;
  179.         /*
  180.          * Normalize the list area so that an integral number of cells are visible
  181.          * on the display and define the visual shape of each cell.
  182.          */
  183.         listHeight = height(listRect);
  184.         listHeight -= (listHeight % listFontHeight);
  185.         listRect.bottom = listRect.top + listHeight;
  186.         SetPt(&cellSize, width(listRect), listFontHeight);
  187.         /*
  188.          * Note: we create a one-column list with both vertical and horizontal
  189.          * scrollbars, then we steal the horizontal scrollbar because we're
  190.          * scrolling within the list cell. Unfortunately, the List Manager only
  191.          * scrolls from one cell (column in the horizontal direction) to another.
  192.          */
  193.         SetRect(&dataBounds, 0, 0, 1, 0);
  194.         logListHandle = LNew(
  195.                 &listRect,                            /* Viewing area                */
  196.                 &dataBounds,                        /* Rows and col's            */
  197.                 cellSize,                            /* Element size                */
  198.                 0,                                    /* No defProc yet            */
  199.                 qd.thePort,                            /* Display window            */
  200.                 TRUE,                                /* Draw it                    */
  201.                 FALSE,                                /* No grow box                */
  202.                 TRUE,                                /* Has horizontal scroll    */
  203.                 TRUE                                /* Has vertical scroll        */
  204.             );
  205.         if (logListHandle == NULL)
  206.             goto failure;
  207.         LIST.selFlags = lOnlyOne;
  208.         LIST.listFlags = lDoVAutoscroll;            /* Vertical autoscroll only    */
  209.         logInfo.logFileRefNum = 0;
  210.         logInfo.logFileVRefNum = 0;
  211.         logInfo.logFileStatus = 0;
  212.         logInfo.logLines = logLines;
  213.         logInfo.fontNumber = listFontNumber;
  214.         logInfo.fontSize = listFontSize;
  215.         if (COLOR_LIST) {
  216.             GetForeColor(&logInfo.foreColor);
  217.             GetBackColor(&logInfo.backColor);
  218.         }
  219.         status = PtrToHand(&logInfo, &logInfoHdl, sizeof logInfo);
  220.         if (status != noErr)
  221.             goto failure;
  222.         LIST.userHandle = (Handle) logInfoHdl;
  223.         HSCROLL = LIST.hScroll;                        /* Grab horizontal scroller    */
  224.         LIST.hScroll = NULL;                        /* Remove it from the list    */
  225.         SetCRefCon(HSCROLL, (long) logListHandle);    /* Link scrollbar to list    */
  226.         status = PtrToHand(gDummyLDEF, &drawProcHdl, sizeof gDummyLDEF);
  227.         if (status != noErr)
  228.             goto failure;
  229.         listProcPtr = (ProcPtr) LogListDefProc;
  230.         BlockMove(&listProcPtr, &((short *) *drawProcHdl)[3], sizeof listProcPtr);
  231.         LIST.listDefProc = drawProcHdl;
  232.         horizontalMax = kMaxHorizontalScroll - width((**HSCROLL).contrlRect);
  233.         if (horizontalMax < 0)
  234.             horizontalMax = 0;
  235.         SetCtlMin(HSCROLL, 0);
  236.         SetCtlMax(HSCROLL, horizontalMax);
  237.         SetCtlValue(HSCROLL, 0);
  238.         HiliteControl(
  239.             HSCROLL,
  240.             (horizontalMax == 0) ? kDisabledControl : kActiveControl
  241.         );
  242.         goto success;
  243. failure:
  244.         if (drawProcHdl != NULL) {
  245.             DisposeHandle((Handle) drawProcHdl);
  246.             LIST.listDefProc = NULL;
  247.         }
  248.         DisposeLog(logListHandle);
  249.         logListHandle = NULL;
  250. success:
  251.         return (logListHandle);
  252. }
  253.  
  254. /*
  255.  * DisposeLog disposes of our private information and then disposes of the list.
  256.  */
  257. void
  258. DisposeLog(
  259.         ListHandle                        logListHandle
  260.     )
  261. {
  262.         if (logListHandle != NULL) {
  263.             if (LIST.userHandle != NULL) {
  264.                 LIST.hScroll = HSCROLL;            /* The list manager disposes    */ 
  265.                 DisposeHandle(LIST.userHandle);
  266.                 LIST.userHandle = NULL;
  267.             }
  268.             LDispose(logListHandle);
  269.         }
  270. }
  271.  
  272. /*
  273.  * UpdateLogWindow explicitly updates the log's window. It is generally called only
  274.  * after Modal Dialogs or Alerts obscured the window.
  275.  */
  276. void
  277. UpdateLogWindow(
  278.         ListHandle                        logListHandle
  279.     )
  280. {
  281.         WindowPtr                        theWindow;
  282.         GrafPtr                            savePort;
  283.         Rect                            viewRect;
  284.         RgnHandle                        listRgn;
  285.         RgnHandle                        clipRgn;
  286.         
  287.         if (logListHandle != NULL) {
  288.             theWindow = (WindowPtr) LIST.port;
  289.             if (EmptyRgn(((WindowPeek) theWindow)->updateRgn) == FALSE) {
  290.                 viewRect = LIST.rView;
  291.                 if (LIST.hScroll != NULL)
  292.                     viewRect.bottom += kScrollBarWidth;
  293.                 if (LIST.hScroll != NULL)
  294.                     viewRect.right += kScrollBarWidth;
  295.                 listRgn = NewRgn();
  296.                 RectRgn(listRgn, &viewRect);
  297.                 SectRgn(listRgn, ((WindowPeek) theWindow)->updateRgn, listRgn);
  298.                 if (EmptyRgn(listRgn) == FALSE) {
  299.                     /*
  300.                      * We have something to redraw. Fake an update event handler.
  301.                      */
  302.                     GetPort(&savePort);
  303.                     SetPort(theWindow);
  304.                     clipRgn = NewRgn();
  305.                     GetClip(clipRgn);
  306.                     SetClip(listRgn);
  307.                     EraseRgn(listRgn);
  308.                     UpdateLog(logListHandle);
  309.                     SetClip(clipRgn);
  310.                     DisposeRgn(clipRgn);
  311.                     ValidRgn(listRgn);
  312.                     SetPort(savePort);
  313.                 }
  314.                 DisposeRgn(listRgn);
  315.             }
  316.         }
  317. }
  318.  
  319. /*
  320.  * UpdateLog redraws the list.
  321.  */
  322. void
  323. UpdateLog(
  324.         ListHandle                        logListHandle
  325.     )
  326. {
  327.         Rect                        viewRect;
  328.         RGBColor                    saveForeColor;
  329.         RGBColor                    saveBackColor;
  330.         
  331.         if (logListHandle != NULL) {
  332.             /*
  333.              * Make sure the list is locked down while we draw. Note that we
  334.              * assume that the application has called UpdateControls, so
  335.              * the horizontal scrollbar is correctly drawn.
  336.              */        
  337.             if (COLOR_LIST) {
  338.                 GetForeColor(&saveForeColor);
  339.                 GetBackColor(&saveBackColor);
  340.                 RGBForeColor(&LOGINFO.foreColor);
  341.                 RGBBackColor(&LOGINFO.backColor);
  342.             }
  343.             viewRect = LIST.rView;
  344.             /*
  345.              * Include the scrollbars in the frame.
  346.              */
  347.             EraseRect(&viewRect);
  348.             InsetRect(&viewRect, -1, -1);
  349.             viewRect.right += kScrollBarOffset;
  350.             viewRect.bottom += kScrollBarOffset;
  351.             FrameRect(&viewRect);
  352.             LUpdate(LIST.port->visRgn, logListHandle);
  353.             if (COLOR_LIST) {
  354.                 RGBForeColor(&saveForeColor);
  355.                 RGBBackColor(&saveBackColor);
  356.             }
  357.         }
  358. }
  359.  
  360. /*
  361.  * ActivateLog activates (or deactivates) the log. Call it on activate and
  362.  * suspendResume events.
  363.  */
  364. void
  365. ActivateLog(
  366.         ListHandle                        logListHandle,
  367.         Boolean                            activating
  368.     )
  369. {
  370.         if (logListHandle != NULL) {
  371.             LActivate(activating, logListHandle);
  372.             HiliteControl(
  373.                 HSCROLL,
  374.                 (activating) ? kActiveControl : kDisabledControl);
  375.         }
  376. }
  377.  
  378. /*
  379.  * MoveLog Repositions the log list area.
  380.  */
  381. void
  382. MoveLog(
  383.         ListHandle                        logListHandle,
  384.         short                            leftEdge,
  385.         short                            topEdge
  386.     )
  387. {
  388.         Rect                            viewRect;
  389.         
  390.         if (logListHandle != NULL) {
  391.             if (LIST.rView.left != leftEdge || LIST.rView.top != topEdge) {
  392.                 viewRect = LIST.rView;
  393.                 InsetRect(&viewRect, -1, -1);
  394.                 InvalRect(&viewRect);
  395.                 OffsetRect(
  396.                     &LIST.rView,
  397.                     leftEdge - LIST.rView.left,
  398.                     topEdge -LIST.rView.top
  399.                 );
  400.                 viewRect = LIST.rView;
  401.                 InsetRect(&viewRect, -1, -1);
  402.                 InvalRect(&viewRect);
  403.                 MoveControl(
  404.                     LIST.vScroll,
  405.                     LIST.rView.right - kScrollBarOffset,
  406.                     LIST.rView.top - 1
  407.                 );
  408.                 MoveControl(
  409.                     HSCROLL,
  410.                     LIST.rView.left - 1,
  411.                     LIST.rView.bottom - kScrollBarOffset
  412.                 );
  413.             }
  414.         }
  415. }
  416.  
  417. /*
  418.  * SizeLog: this is the list rectangle size and does not include the
  419.  * horizontal and vertical scrollbars.
  420.  */
  421. void
  422. SizeLog(
  423.         ListHandle                        logListHandle,
  424.         short                            newWidth,
  425.         short                            newHeight
  426.     )
  427. {
  428.         Rect                            viewRect;
  429.         Point                            cellSize;
  430.         short                            horizontalMax;
  431.         
  432.         if (logListHandle != NULL) {
  433.             viewRect = LIST.rView;
  434.             InsetRect(&viewRect, -1, -1);
  435.             InvalRect(&viewRect);
  436.             /*
  437.              * We put the horizontal scrollbar back into the list record so that
  438.              * the list manager kindly resizes it. Then we take it out again.
  439.              */
  440.             LIST.hScroll = HSCROLL;
  441.             LSize(newWidth, newHeight, logListHandle);
  442.             LIST.hScroll = NULL;
  443.             cellSize = LIST.cellSize;
  444.             cellSize.h = width(LIST.rView);
  445.             LCellSize(cellSize, logListHandle);
  446.             horizontalMax = kMaxHorizontalScroll - width((**HSCROLL).contrlRect);
  447.             if (horizontalMax < 0)
  448.                 horizontalMax = 0;
  449.             SetCtlMax(HSCROLL, horizontalMax);
  450.             HiliteControl(
  451.                 HSCROLL,
  452.                 (horizontalMax == 0) ? kDisabledControl : kActiveControl
  453.             );
  454.             viewRect = LIST.rView;
  455.             InsetRect(&viewRect, -1, -1);
  456.             InvalRect(&viewRect);
  457.         }
  458. }
  459.  
  460. /*
  461.  * DoClickInLog: call this when there is a mouse down in the log area (or in
  462.  * one of the scrollbars.
  463.  */
  464. Boolean
  465. DoClickInLog(
  466.         ListHandle                        logListHandle,
  467.         const EventRecord                *eventRecord
  468.     )
  469. {
  470. #define EVENT    (*eventRecord)
  471.  
  472.         Point                            mousePt;
  473.         Boolean                            result;
  474.         Rect                            viewRect;
  475.         short                            part;
  476.         ControlHandle                    theControl;
  477.  
  478.         if (logListHandle == NULL)
  479.             result = FALSE;
  480.         else {
  481.             mousePt = EVENT.where;
  482.             GlobalToLocal(&mousePt);
  483.             /*
  484.              * Handle clicks in the horizontal scrollbar: Do not pass them through
  485.              * LClick, as it does not do what we want and besides, the scrollbar
  486.              * isn't there any more.
  487.              */
  488.             if (PtInRect(mousePt, &(**HSCROLL).contrlRect)) {
  489.                 part = FindControl(mousePt, (**HSCROLL).contrlOwner, &theControl);
  490.                 if (part >= 0 && theControl == HSCROLL) {
  491.                     if (part == inThumb) {
  492.                         if (TrackControl(theControl, mousePt, NULL))
  493.                             ScrollLogList(theControl);
  494.                     }
  495.                     else {
  496.                         TrackControl(
  497.                             theControl,
  498.                             mousePt,
  499.                             NewControlActionProc(ScrollLogAction)
  500.                         );
  501.                     }
  502.                 }
  503.             }
  504.             else {
  505.                 viewRect = LIST.rView;
  506.                 viewRect.right += kScrollBarOffset;
  507.                 result = PtInRect(mousePt, &viewRect);
  508.                 if (result)
  509.                     (void) LClick(mousePt, EVENT.modifiers, logListHandle);
  510.             }
  511.         }
  512.         return (result);
  513. #undef EVENT
  514. }
  515.  
  516. /*
  517.  * Log errors.
  518.  */
  519. void
  520. LogStatus(
  521.         ListHandle                        logListHandle,
  522.         OSErr                            theError,
  523.         const StringPtr                    infoText
  524.     )
  525. {
  526.         Handle                            macErrorHdl;
  527.         Str255                            msg;
  528.         Str15                            errorValue;
  529.         
  530.         if (logListHandle != NULL && theError != noErr) {
  531.             pstrcpy(msg, infoText);
  532.             pstrcat(msg, "\p: ");
  533.             NumToString(theError, errorValue);
  534.             pstrcat(msg, errorValue);
  535.             macErrorHdl = GetResource('Estr', theError);
  536.             if (macErrorHdl != NULL) {
  537.                 pstrcat(msg, "\p ");
  538.                 pstrcat(msg, (StringPtr) *macErrorHdl);
  539.                 ReleaseResource(macErrorHdl);
  540.             }
  541.             DisplayLogString(logListHandle, msg);
  542.         }
  543. }
  544.  
  545. /*
  546.  * DisplayLogString
  547.  * Call this function to store a string in the list.
  548.  */
  549. void
  550. DisplayLogString(
  551.         ListHandle                        logListHandle,
  552.         const StringPtr                    theString
  553.     )
  554. {
  555.         short                        theRow;
  556.         Boolean                        scrollAtBottom;
  557.         
  558.         if (logListHandle != NULL) {
  559.             /*
  560.              * If there are already logLines in the
  561.              * list, delete the first row of the list.
  562.              * Then, in any case, append this datum at the
  563.              * bottom.
  564.              *
  565.              * The scroll bars are managed as follows:
  566.              * scroll bar is at the bottom, the new datum
  567.              * is selected and autoscrolled into view.
  568.              * Otherwise, the current cell is unchanged.
  569.              */
  570.             theRow = LIST.dataBounds.bottom;
  571.             scrollAtBottom =
  572.                 (GetCtlValue(LIST.vScroll) == GetCtlMax(LIST.vScroll));
  573.             if (theRow >= LOGINFO.logLines)
  574.                 LDelRow(1, 0, logListHandle);
  575.             if (AddStringToList(logListHandle, theString) == noErr) {
  576.                 if (scrollAtBottom)
  577.                     LScroll(0, LIST.dataBounds.bottom - GetCtlValue(LIST.vScroll), logListHandle);
  578.                 if (LOGINFO.logFileRefNum != 0 && LOGINFO.logFileStatus == noErr)
  579.                     WriteLogLine(logListHandle, theString);
  580.             }
  581.         }
  582. failure:
  583.         return;
  584. }
  585.  
  586. static OSErr
  587. AddStringToList(
  588.         ListHandle                        logListHandle,
  589.         ConstStr255Param                theString
  590.     )
  591. {
  592.         StringHandle                    stringHandle;
  593.         Cell                            theCell;
  594.         
  595.         stringHandle = NewString(theString);
  596.         if (stringHandle != NULL) {
  597.             theCell.h = 0;
  598.             theCell.v = LAddRow(1, LIST.dataBounds.bottom, logListHandle);
  599.             if (MemError() == noErr)
  600.                 LSetCell(&stringHandle, sizeof stringHandle, theCell, logListHandle);
  601.         }
  602.         return (MemError());
  603. }
  604.  
  605. /*
  606.  * ScrollLogAction is called by the Toolbox while executing the TrackControl
  607.  * routine.  It has to take care of scrolling the log when the user clicks on the
  608.  * up/down arrow or page parts of the scroll bar.
  609.  */
  610. static pascal void
  611. ScrollLogAction(
  612.         register ControlHandle            theControl,
  613.         short                            partcode
  614.     )
  615. {
  616.         short                            delta;
  617.         
  618.         delta = (width((**theControl).contrlRect) * 7) / 8;
  619.         switch (partcode) {
  620.         case inUpButton:    delta = -CharWidth('M');    break;
  621.         case inPageUp:        delta = -(delta);            break;        
  622.         case inDownButton:    delta = CharWidth('M');        break;
  623.         case inPageDown:    /* All set */                break;
  624.         default:            return;        /* Mouse exited control    */
  625.         }
  626.         SetCtlValue(theControl, GetCtlValue(theControl) + delta);
  627.         ScrollLogList(theControl);
  628. }
  629.  
  630. /*
  631.  * ScrollLogList scrolls the list rectangle in the proper direction and updates
  632.  * the list's horizontal indentation to match.
  633.  */
  634. static void
  635. ScrollLogList(
  636.         ControlHandle                    theControl
  637.     )
  638. {
  639.         ListHandle                        logListHandle;
  640.         short                            delta;
  641.         RgnHandle                        clipRgn;
  642.         RgnHandle                        updateRgn;
  643.         Rect                            viewRect;
  644.         
  645.         logListHandle = (ListHandle) GetCRefCon(theControl);
  646.         /*
  647.          * LIST.indent.h is negative when the cell is scrolled left. Get the
  648.          * amount it's currently scrolled (as a positive value) and set delta
  649.          * to the amount that must be scrolled. Delta will be positive to
  650.          * scroll right (which means that the scrollbar has moved left).
  651.          */
  652.         delta = kZeroIndent - LIST.indent.h - GetCtlValue(theControl);
  653.         if (delta != 0) {
  654.             /*
  655.              * We need to scroll the list cells. Get a clip rectangle so the
  656.              * scrolling is limited to the drawing area, scroll it, and update
  657.              * the stuff that came into view.
  658.              */
  659.             viewRect = LIST.rView;
  660.             clipRgn = NewRgn();
  661.             updateRgn = NewRgn();
  662.             GetClip(clipRgn);
  663.             ClipRect(&viewRect);
  664.             ScrollRect(&viewRect, delta, 0, updateRgn);
  665.             LIST.indent.h += delta;
  666.             LUpdate(updateRgn, logListHandle);
  667.             SetClip(clipRgn);
  668.             DisposeRgn(updateRgn);
  669.             DisposeRgn(clipRgn);
  670.         }
  671. }
  672.         
  673. /*
  674.  * Draw the string stored in the list. The only difference between this function
  675.  * and a "normal" LDEF is that we don't visually indicate selection.
  676.  */
  677. static pascal void
  678. LogListDefProc(
  679.         short                            listMessage,
  680.         Boolean                            listSelect,
  681.         Rect                            *listRect,
  682.         Cell                            listCell,
  683.         short                            listDataOffset,
  684.         short                            listDataLen,
  685.         ListHandle                        logListHandle
  686.     )
  687. {
  688.         char                            stringLockState;
  689.         RGBColor                        saveForeColor;
  690.         RGBColor                        saveBackColor;
  691.         StringHandle                    stringHandle;
  692.         
  693.         if (0) {
  694.             listCell;
  695.         }
  696.         /*
  697.          * If the userHandle isn't setup, do nothing: this is an initialization
  698.          * or a spurious command while we're disposing of the list.
  699.          */
  700.         if (LIST.userHandle != NULL) {
  701.             TextFont(LOGINFO.fontNumber);
  702.             TextSize(LOGINFO.fontSize);
  703.             if (COLOR_LIST) {
  704.                 GetForeColor(&saveForeColor);
  705.                 GetBackColor(&saveBackColor);
  706.                 RGBForeColor(&LOGINFO.foreColor);
  707.                 RGBBackColor(&LOGINFO.backColor);
  708.             }
  709.             switch (listMessage) {
  710.             case lInitMsg:
  711.                 break;
  712.             case lDrawMsg:
  713.                 EraseRect(listRect);
  714.                 if (listDataLen == sizeof stringHandle) {
  715.                     BlockMove(
  716.                         (*LIST.cells) + listDataOffset,
  717.                         &stringHandle,
  718.                         sizeof stringHandle
  719.                     );
  720.                     /*
  721.                      * We don't indent in the vertical direction: by default,
  722.                      * it contains the font ascent which is fine for DrawText
  723.                      */
  724.                     stringLockState = HGetState((Handle) stringHandle);
  725.                     HLock((Handle) stringHandle);
  726.                     MoveTo(
  727.                         listRect->left + LIST.indent.h,
  728.                         listRect->top + LIST.indent.v
  729.                     );
  730.                     DrawString(*stringHandle);
  731.                     HSetState((Handle) stringHandle, stringLockState);
  732.                 }
  733.                 if (listSelect == FALSE)
  734.                     break;
  735.                 /* Continue to do hilite */
  736.             case lHiliteMsg:
  737. #if 0        /* Hiliting is disabled */
  738. #ifdef THINK_C
  739.                 HiliteMode &= ~(1 << hiliteBit);
  740. #else /* MPW */
  741.                 *((char *) HiliteMode) &= ~(1 << hiliteBit); /* Inside Mac V-61    */
  742. #endif
  743.                 InvertRect(listRect);
  744. #endif
  745.                 break;
  746.             }
  747.             if (COLOR_LIST) {
  748.                 RGBForeColor(&saveForeColor);
  749.                 RGBBackColor(&saveBackColor);
  750.             }
  751.         }
  752. }
  753.  
  754. StringHandle
  755. GetLogStringHandle(
  756.         ListHandle                    logListHandle,
  757.         short                        theRow
  758.     )
  759. {
  760.         Cell                        theCell;
  761.         StringHandle                result;
  762.         short                        dataLength;
  763.         
  764.         dataLength = sizeof result;
  765.         theCell.h = 0;
  766.         theCell.v = theRow;
  767.         LGetCell(&result, &dataLength, theCell, logListHandle);
  768.         if (dataLength != sizeof result)
  769.             result = NULL;
  770.         return (result);
  771. }
  772.  
  773. /*
  774.  * Prompt the user for a file name and write the current log contents
  775.  * to the chosen file. Returns an error status (noErr is ok). This function
  776.  * returns userCanceledErr if the user cancelled the SFPutFile dialog.
  777.  */
  778. OSErr
  779. SaveLogFile(
  780.         ListHandle                        logListHandle,
  781.          ConstStr255Param                dialogPromptString,
  782.         ConstStr255Param                defaultFileName,
  783.         OSType                            creatorType
  784.     )
  785. {
  786.         Point                            where;
  787.         SFReply                            reply;
  788.         DialogTHndl                        dialog;
  789.         Rect                            box;
  790.         OSErr                            status;
  791.         
  792.         /*
  793.          * Center the dialog
  794.          */
  795.         dialog = (DialogTHndl) GetResource('DLOG', putDlgID);
  796.         if (dialog == NULL)                             /* No such dialog!    */
  797.             SetRect(&box, 0, 0, 0, 0);                /* Center on screen    */
  798.         else {
  799.             box = (*dialog)->boundsRect;             /* Dialog shape        */
  800.             ReleaseResource((Handle) dialog);        /* Done with dialog    */
  801.         }
  802.         SetPt(&where,
  803.             (width(qd.thePort->portRect) - width(box)) / 2,
  804.             ((height(qd.thePort->portRect) - GetMBarHeight()) / 3)
  805.                 + GetMBarHeight()
  806.         );
  807.         SFPutFile(where, dialogPromptString, defaultFileName, NULL, &reply);
  808.         if (reply.good == FALSE)
  809.             status = userCanceledErr;
  810.         else {
  811.             SetCursor(*GetCursor(watchCursor));
  812.             status = CreateLogFile(
  813.                         logListHandle,
  814.                         creatorType,
  815.                         reply.fName,
  816.                         reply.vRefNum
  817.                     );
  818.             InitCursor();
  819.             if (status != noErr)
  820.                 (void) FSDelete(reply.fName, reply.vRefNum);
  821.         }
  822.         return (status);
  823. }
  824.  
  825. /*
  826.  * Create a log on the specified file and volume. Normally, called only
  827.  * by SaveLogFile.
  828.  */
  829. OSErr
  830. CreateLogFile(
  831.         ListHandle                        logListHandle,
  832.         OSType                            creatorType,
  833.         ConstStr255Param                fileName,
  834.         short                            vRefNum
  835.     )
  836. {
  837.         OSErr                            status;
  838.         short                            refNum;
  839.         
  840.         /*
  841.          * Create the file, elmininating any duplicate.
  842.          */
  843.         if (creatorType == 0)
  844.             creatorType = 'ttxt';                    /* TeachText            */
  845.         status = Create(fileName, vRefNum, creatorType, 'TEXT');
  846.         if (status == dupFNErr) {                    /* Exists already?        */
  847.             status = FSDelete(fileName, vRefNum);
  848.             if (status == noErr)
  849.                 status = Create(fileName, vRefNum, creatorType, 'TEXT');
  850.         }
  851.         if (status == noErr)
  852.             status = FSOpen(fileName, vRefNum, &refNum);
  853.         if (status == noErr) {
  854.             LOGINFO.logFileRefNum = refNum;
  855.             LOGINFO.logFileVRefNum = vRefNum;
  856.             LOGINFO.logFileStatus = noErr;
  857.             WriteCurrentLog(logListHandle);
  858.             status = LOGINFO.logFileStatus;
  859.         }
  860.         if (status != noErr) {
  861.             (void) CloseLogFile(logListHandle);
  862.             LogStatus(logListHandle, status, "\pCreateLogFile failed");
  863.         }
  864.         return (status);
  865. }
  866.  
  867. /*
  868.  * Write the current contents of the log file. Any error will be
  869.  * in LOGINFO.logFileStatus.
  870.  */
  871. void
  872. WriteCurrentLog(
  873.         ListHandle                        logListHandle
  874.     )
  875. {
  876.         short                            theRow;
  877.         long                            fileLength;
  878.         StringHandle                    stringHandle;
  879.         char                            stringLockState;
  880.  
  881.         
  882.         theRow = LIST.dataBounds.top;
  883.         while (LOGINFO.logFileStatus == noErr && theRow < LIST.dataBounds.bottom) {
  884.             stringHandle = GetLogStringHandle(logListHandle, theRow);
  885.             if (stringHandle != NULL) {
  886.                 stringLockState = HGetState((Handle) stringHandle);
  887.                 HLock((Handle) stringHandle);
  888.                 fileLength = (*stringHandle)[0];
  889.                 LOGINFO.logFileStatus = FSWrite(
  890.                     LOGINFO.logFileRefNum,
  891.                     &fileLength,
  892.                     &(*stringHandle)[1]
  893.                 );
  894.                 HSetState((Handle) stringHandle, stringLockState);
  895.                 if (LOGINFO.logFileStatus == noErr) {
  896.                     fileLength = 1;
  897.                     LOGINFO.logFileStatus = FSWrite(
  898.                                 LOGINFO.logFileRefNum,
  899.                                 &fileLength,
  900.                                 endOfLine
  901.                             );
  902.                 }
  903.             }
  904.             ++theRow;
  905.         };
  906. }        
  907.  
  908. void
  909. WriteLogLine(
  910.         ListHandle                        logListHandle,
  911.         ConstStr255Param                theText
  912.     )
  913. {
  914.         long                            fileLength;
  915.         
  916.         if (LOGINFO.logFileRefNum != 0 && LOGINFO.logFileStatus == noErr) {
  917.             fileLength = theText[0];
  918.             LOGINFO.logFileStatus = FSWrite(
  919.                         LOGINFO.logFileRefNum,
  920.                         &fileLength,
  921.                         &theText[1]
  922.                     );
  923.             if (LOGINFO.logFileStatus == noErr) {
  924.                 fileLength = 1;
  925.                 LOGINFO.logFileStatus = FSWrite(
  926.                             LOGINFO.logFileRefNum,
  927.                             &fileLength,
  928.                             endOfLine
  929.                         );
  930.             }
  931.             if (LOGINFO.logFileStatus != noErr) {
  932.                 CloseLogFile(logListHandle);
  933.                 LogStatus(
  934.                     logListHandle,
  935.                     LOGINFO.logFileStatus,
  936.                     "\pCan't write to log file"
  937.                 );
  938.             }
  939.         }
  940. }
  941.  
  942. OSErr
  943. CloseLogFile(
  944.         ListHandle                        logListHandle
  945.     )
  946. {
  947.         OSErr                            status;
  948.         
  949.         status = noErr;
  950.         if (LOGINFO.logFileRefNum != 0) {
  951.             status = FSClose(LOGINFO.logFileRefNum);
  952.             if (status == noErr)
  953.                 status = FlushVol(NULL, LOGINFO.logFileVRefNum);
  954.         }
  955.         LOGINFO.logFileRefNum = 0;
  956.         LOGINFO.logFileVRefNum = 0;
  957.         if (status != noErr)
  958.             LogStatus(logListHandle, status, "\pCan't close log file");
  959.         if (LOGINFO.logFileStatus == noErr)
  960.             LOGINFO.logFileStatus = status;
  961.         status = LOGINFO.logFileStatus;
  962.         LOGINFO.logFileStatus = noErr;
  963.         return (status);
  964. }
  965.  
  966. OSErr
  967. GetLogFileError(
  968.         ListHandle                        logListHandle
  969.     )
  970. {
  971.         return (LOGINFO.logFileStatus);
  972. }
  973.  
  974. Boolean
  975. HasLogFile(
  976.         ListHandle                        logListHandle
  977.     )
  978. {
  979.         return (LOGINFO.logFileRefNum != 0);
  980. }
  981.  
  982. /*
  983.  * Copyright © 1993, Apple Computer Inc. All Rights reserved.
  984.  *
  985.  * This started life as a generic print driver written by Rich Siegel and posted
  986.  * to Usenet in around 1988 or so. It has been extensively rewritten, but not
  987.  * necessarily improved.
  988.  */
  989.  
  990. /*
  991.  *        OSErr
  992.  *        PrintDriver(
  993.  *                THPrint            hPrint,
  994.  *                void            *clientData,
  995.  *                Boolean            doStyle,
  996.  *                OSErr            (*userPrepProc)(
  997.  *                                        THPrnt        hPrint,
  998.  *                                        void        *clientData
  999.  *                                    ),
  1000.  *                OSErr            (*userPageProc)(
  1001.  *                                        THPrnt        hPrint,
  1002.  *                                        void        *clientData,
  1003.  *                                        const Rect    *pageRect,
  1004.  *                                        short        pageNum
  1005.  *                                    )
  1006.  *            );
  1007.  *        hPrint        A Print Manager handle.  If NULL, ReportPrintDriver
  1008.  *                    will allocate a handle, call the Page Setup dialog,
  1009.  *                    and dispose of the handle on exit.
  1010.  *        clientData    A longword parameter that becomes a parameter to the
  1011.  *                    prepProc and pageProc functions. For the LogManager,
  1012.  *                    it is the ListHandle.
  1013.  *        doStyle        If TRUE, the page setup modal dialog is always called.
  1014.  *                    Otherwise, it's called only if the print record is invalid.
  1015.  *
  1016.  *        userPrepProc A user-provided function that will perform print setup
  1017.  *                    initializations.  It must be defined
  1018.  *                        OSErr            UserPrepProc(
  1019.  *                                THPrint        hPrint,
  1020.  *                                void        *clientData
  1021.  *                            );
  1022.  *                    UserPrepProc returns noErr on success or an error status that
  1023.  *                    will  be returned to the PrintDriver caller on failure.
  1024.  *                    UserPrepProc must set (**hPrint).prJob.iLstPage  to the correct
  1025.  *                    number of pages in the document. If all pages are specified
  1026.  *                    by the user dialog, this field will be 999 when UserPrepProc
  1027.  *                    is called; it must then be re-set to the correct value.
  1028.  *        userPageProc A user-provided function that draws the page.  It is defined:
  1029.  *                        OSErr            UserPageProc(
  1030.  *                                THPrint        hPrint,
  1031.  *                                void        *clientData,
  1032.  *                                Rect        *pageRect,
  1033.  *                                short        pageNum
  1034.  *                        );
  1035.  *                    UserPageProc returns noErr if it completed correctly, or an
  1036.  *                    error code that will be returned to the user.  It should
  1037.  *                    return iPrAbort on user-specified aborts.
  1038.  */
  1039.  
  1040. /*
  1041.  * Note: the serial stuff has not been tested in several years.
  1042.  */
  1043. #ifndef bDevCItoh
  1044. #define bDevCItoh        1                /* ImageWriter                */
  1045. #endif
  1046. #ifndef bDevLaser
  1047. #define bDevLaser        3                /* LaserWriter                */
  1048. #endif
  1049. #define ImageWriter        (bDevCItoh)
  1050. #define    LaserWriter        (bDevLaser)
  1051.  
  1052. OSErr                                PrintLog(
  1053.         ListHandle                        logListHandle,
  1054.         THPrint                            hPrint
  1055.     );
  1056. OSErr                                LogPrepProc(
  1057.         THPrint                            hPrint,
  1058.         void                            *clientData
  1059.     );
  1060. OSErr                                LogPageProc(
  1061.         THPrint                            hPrint,
  1062.         void                            *clientData,
  1063.         const Rect                        *pageRect,
  1064.         short                            pageNumber
  1065.     );
  1066. OSErr                                PrintDriver(
  1067.         THPrint                            hPrint,
  1068.         Boolean                            doStyleDialog,
  1069.         void                            *clientData,
  1070.         OSErr                            (*userPrepProc)(
  1071.                         THPrint        hPrint,
  1072.                         void        *clientData
  1073.                     ),
  1074.         OSErr                            (*userPageProc)(
  1075.                         THPrint        hPrint,
  1076.                         void        *clientData,
  1077.                         const Rect    *pageRect,
  1078.                         short        pageNumber
  1079.                     )
  1080.     );
  1081.  
  1082. /*
  1083.  * PrintLog, LogPrepProc, and LogPageProc are specific to the LogManager.
  1084.  */
  1085.  
  1086. OSErr
  1087. PrintLog(
  1088.         ListHandle                        logListHandle,
  1089.         THPrint                            hPrint
  1090.     )
  1091. {
  1092.         OSErr            status;
  1093.         
  1094.         status = PrintDriver(
  1095.                     hPrint,
  1096.                     FALSE,
  1097.                     logListHandle,
  1098.                     LogPrepProc,
  1099.                     LogPageProc
  1100.                 );
  1101.         return (status);
  1102. }
  1103.  
  1104. OSErr
  1105. LogPrepProc(
  1106.         THPrint                        hPrint,
  1107.         void                        *clientData
  1108.     )
  1109. {
  1110.         unsigned short                linesInLog;
  1111.         unsigned short                linesPerPage;
  1112.         unsigned short                pagesInLog;
  1113.         Rect                        printRect;
  1114. #define logListHandle    ((ListHandle) clientData)
  1115.  
  1116.         /*
  1117.          * We could add a page header here, of course.
  1118.          */
  1119.         printRect = (**hPrint).prInfo.rPage;
  1120.         linesInLog = height(LIST.dataBounds);
  1121.         linesPerPage = height(printRect) / LIST.cellSize.v;
  1122.         pagesInLog = (linesInLog + linesPerPage - 1) / linesPerPage;
  1123.         (**hPrint).prJob.iLstPage = pagesInLog;
  1124.         return (noErr);
  1125. #undef logListHandle
  1126. }
  1127.  
  1128. OSErr
  1129. LogPageProc(
  1130.         THPrint                            hPrint,
  1131.         void                            *clientData,
  1132.         const Rect                        *pageRect,
  1133.         short                            pageNumber
  1134.     )
  1135. {
  1136.         unsigned short                    linesPerPage;
  1137.         short                            lastCell;
  1138.         Point                            drawLoc;
  1139.         FontInfo                        info;
  1140.         short                            theRow;
  1141.         StringHandle                    stringHandle;
  1142.         char                            stringLockState;
  1143. #define logListHandle    ((ListHandle) clientData)
  1144.  
  1145.         if (0) {
  1146.             hPrint;
  1147.         }
  1148.         linesPerPage = height(*pageRect) / LIST.cellSize.v;
  1149.         TextFont(LOGINFO.fontNumber);
  1150.         TextSize(LOGINFO.fontSize);
  1151.         GetFontInfo(&info);
  1152.         theRow = (pageNumber - 1) * linesPerPage + LIST.dataBounds.top;
  1153.         lastCell = theRow + linesPerPage - 1;
  1154.         if (lastCell > LIST.dataBounds.bottom)
  1155.             lastCell = LIST.dataBounds.bottom;
  1156.         SetPt(
  1157.             &drawLoc,
  1158.             LIST.indent.h,
  1159.             pageRect->top + info.ascent
  1160.         );
  1161.         /*
  1162.          * We could add a page header here, of course.
  1163.          */
  1164.         for (; theRow < lastCell; ++theRow) {
  1165.             stringHandle = GetLogStringHandle(logListHandle, theRow);
  1166.             if (stringHandle != NULL) {
  1167.                 stringLockState = HGetState((Handle) stringHandle);
  1168.                 HLock((Handle) stringHandle);
  1169.                 MoveTo(drawLoc.h, drawLoc.v);
  1170.                 DrawString(*stringHandle);
  1171.                 HSetState((Handle) stringHandle, stringLockState);
  1172.             }
  1173.             drawLoc.v += LIST.cellSize.v;
  1174.         };
  1175.         return (PrError());
  1176. }
  1177.  
  1178. /*
  1179.  * Rich Siegel's generic printer driver, somewhat modified.
  1180.  */
  1181. OSErr
  1182. PrintDriver(
  1183.         THPrint                            hPrint,
  1184.         Boolean                            doStyleDialog,
  1185.         void                            *clientData,
  1186.         OSErr                            (*userPrepProc)(
  1187.                         THPrint        hPrint,
  1188.                         void        *clientData
  1189.                     ),
  1190.         OSErr                            (*userPageProc)(
  1191.                         THPrint        hPrint,
  1192.                         void        *clientData,
  1193.                         const Rect    *pageRect,
  1194.                         short        pageNumber
  1195.                     )
  1196.     )
  1197. {
  1198.         OSErr                status;                /* Random status                */
  1199.         Boolean                ourPrintHandle;        /* Did we allocate hPrint        */
  1200.         Boolean                printIsOpen;        /* PROpen-PRClose                 */        
  1201.         Boolean                docIsOpen;            /* PROpenDoc-PRCloseDoc            */
  1202.         Boolean                pageIsOpen;            /* PROpenPage-PRClosePage        */
  1203.         short                nCopies;            /* Number of copies             */
  1204.         short                printDevice;        /* What kind of printer            */
  1205.         Boolean                draftMode;            /* Draft or Spool?                */
  1206.         TPPrPort            printPort;            /* The print port                */
  1207.         TPrStatus            printStatus;        /* PrPicFile status info        */
  1208.         GrafPtr                savePort;            /* Old GrafPort                    */
  1209.         short                iCopy;                /* Which copy                    */
  1210.         short                page;                /* Which page                     */
  1211.         Rect                pageRect;            /* Current page image rect        */
  1212. /*
  1213.  * This macro exits the print handler on any error.
  1214.  */
  1215. #define    CheckError(s)    do {                \
  1216.         if ((status = (s)) != noErr)        \
  1217.             goto exit;                        \
  1218.     } while (0);
  1219.  
  1220.         GetPort(&savePort);
  1221.         status = noErr;
  1222.         printIsOpen = FALSE;
  1223.         docIsOpen = FALSE;
  1224.         pageIsOpen = FALSE;
  1225.         ourPrintHandle = (hPrint == NULL);
  1226.         PrOpen();
  1227.         CheckError(PrError());
  1228.         printIsOpen = TRUE;
  1229.         /*
  1230.          * Allocate the print handle if necessary.  memFullErr on failure
  1231.          * it failed.
  1232.          */
  1233.         if (ourPrintHandle) {
  1234.             hPrint = (THPrint) NewHandle(sizeof (TPrint));
  1235.             if (hPrint == NULL) {
  1236.                 status = MemError();
  1237.                 goto exit;
  1238.             }
  1239.             PrintDefault(hPrint);
  1240.         }
  1241.         /*
  1242.          * Validate the print handle and call the Style Dialog if necessary.  If
  1243.          * the user cancels, just exit (noErr). Then call the job dialog to get
  1244.          * the number of copies. Exit (noErr) if the user Cancels. (Strictly
  1245.          * speaking, we could exit with userCanceledErr, but that just forces
  1246.          * the caller to ignore this informational status.
  1247.          */
  1248.         if (PrValidate(hPrint) || doStyleDialog) {
  1249.             if (PrStlDialog(hPrint) == FALSE)
  1250.                 goto exit;
  1251.         }
  1252.         if (PrJobDialog(hPrint) == FALSE)
  1253.             goto exit;
  1254.         /*
  1255.          * Our setup is done. call the user's prep procedure and exit on errors.
  1256.          */
  1257.         SetCursor(*GetCursor(watchCursor));
  1258.         CheckError((*userPrepProc)(hPrint, clientData));
  1259.         /*
  1260.          * Grab a few interesting parameters:
  1261.          *    printDevice    Which printer we're using
  1262.          *    is_draftMode    TRUE if we're in draft-mode.
  1263.          *    nCopies            We do copies if we're in draft-mode on an Imagewriter.
  1264.          *                    Otherwise, the spooler does it for us. This hasn't
  1265.          *                    been tested in years.
  1266.          */
  1267.         printDevice = ((**hPrint).prStl.wDev >> 8) & 0xFF;
  1268.         draftMode = (**hPrint).prJob.bJDocLoop == bDraftLoop;
  1269.         if (draftMode && printDevice == ImageWriter)
  1270.             nCopies = (**hPrint).prJob.iCopies;
  1271.         else {
  1272.             nCopies = 1;
  1273.         }
  1274.         /*
  1275.          * Printing begins here.  PrOpenDoc sets the port.
  1276.          */
  1277.         printPort = PrOpenDoc(hPrint, NULL, NULL);
  1278.         docIsOpen = TRUE;
  1279.         CheckError(PrError());
  1280.         for (iCopy = 1; iCopy <= nCopies; iCopy++) {
  1281.             for (page = (**hPrint).prJob.iFstPage;
  1282.                         page <= (**hPrint).prJob.iLstPage;
  1283.                         page++) {
  1284.                 SetCursor(*GetCursor(watchCursor));
  1285.                 /*
  1286.                  * Print the current page.
  1287.                  */
  1288.                 PrOpenPage(printPort, NULL);
  1289.                 pageIsOpen = TRUE;
  1290.                 pageRect = (**hPrint).prInfo.rPage;
  1291.                 status = (*userPageProc)(hPrint, clientData, &pageRect, page);
  1292.                 PrClosePage(printPort);
  1293.                 pageIsOpen = FALSE;
  1294.                 CheckError(status);
  1295.             }
  1296.         }
  1297.         SetPort(savePort);
  1298.         PrCloseDoc(printPort);
  1299.         docIsOpen = FALSE;
  1300.         status = PrError();
  1301.         if (draftMode == FALSE && status == noErr) {
  1302.             PrPicFile(hPrint, NULL, NULL, NULL, &printStatus);
  1303.             status = PrError();
  1304.         }
  1305.         /*
  1306.          * Everyone exits here
  1307.          */
  1308. exit:    SetPort(savePort);
  1309.         InitCursor();
  1310.         if (pageIsOpen)
  1311.             PrClosePage(printPort);
  1312.         if (docIsOpen)
  1313.             PrCloseDoc(printPort);
  1314.         if (printIsOpen)
  1315.             PrClose();
  1316.         if (ourPrintHandle && hPrint != NULL)
  1317.             DisposHandle((Handle) hPrint);
  1318.         return (status);
  1319. }
  1320.  
  1321.